import numpy as np 

# 评分矩阵（用户-物品）
ratings = np.array([
    [5, 3, 0, 1],  # 用户 0
    [4, 0, 0, 1],  # 用户 1
    [1, 1, 0, 5],  # 用户 2
    [1, 0, 0, 4],  # 用户 3
    [0, 1, 5, 4]   # 用户 4
])

def user_similarity(ratings):
    """计算用户相似度矩阵（基于共同评分项目的余弦相似度）"""
    n_users = ratings.shape[0] # 用户个数
    similarity = np.zeros((n_users,n_users)) # 初始化用户相似度矩阵为(n_users,n_users)全零矩阵

    for i in range(n_users):
        for j in range(i+1,n_users): # 只计算上三角，避免重复计算
            # 找出共同评分的项目
            common_mask = (ratings[i]>0) & (ratings[j]>0)
            if np.any(common_mask):
                # 提取公共评分向量
                vec_i = ratings[i,common_mask]
                vec_j = ratings[j,common_mask]

                # 计算余弦相似度
                dot_product = np.dot(vec_i,vec_j)
                norm_i = np.linalg.norm(vec_i)
                norm_j = np.linalg.norm(vec_j)

                if norm_i>0 and norm_j>0:
                    sim = dot_product/(norm_i*norm_j)
                    similarity[i,j] = sim
                    similarity[j,i] = sim # 对称矩阵
                    

    return similarity

def predict_rating(ratings,sim_matrix,user_id,item_id,k=2):
    """预测指定用户对物品的评分"""
    # 计算目标用户的平均评分(只计算评价过的物品的评分)
    user_ratings = ratings[user_id]
    user_rated_items = user_ratings>0

    if np.any(user_rated_items):
        user_mean = np.mean(user_ratings[user_rated_items]) 
    else:
        user_mean = 0

    # 获取所有其他用户的索引
    n_users = ratings.shape[0]
    other_users = np.array([i for i in range(n_users) if i !=user_id])
    
    # 仅考虑对目标物品有评分的用户
    rated_mask = ratings[other_users,item_id]>0
    rated_users = other_users[rated_mask]
      
    if len(rated_users)==0: # 没有用户对该物品评分
        return user_mean   
    
    # 获取这些用户与目标用户的相似度
    user_similarities = sim_matrix[user_id,rated_users]

    # 按照相似度降序排序(找到相似度最高的k个近邻)
    sorted_indices = np.argsort(user_similarities)[::-1]
    top_k = min(k,len(sorted_indices))
    neighbor_indices = rated_users[sorted_indices[:top_k]]
    
    # 获取邻居的评分和相似度
    neighbor_ratings = ratings[neighbor_indices,item_id]
    neighbor_sims = sim_matrix[user_id,neighbor_indices]
    
    # 计算每个近邻的平均评分
    neighbor_means = []
    for idx in neighbor_indices:
        u_ratings = ratings[idx]
        mask = u_ratings>0
        if np.any(mask):
            neighbor_means.append(np.mean(u_ratings[mask]))
        else:
            neighbor_means.append(0)
    neighbor_means = np.array(neighbor_means)
    
    # 计算偏差调整后的评分
    adjusted_ratings = neighbor_ratings-neighbor_means

    # 计算加权平均偏差
    numerator = np.dot(adjusted_ratings,neighbor_sims)    
    denominator = np.sum(np.abs(neighbor_sims))
    if denominator > 0:
        # 计算预测评分并限制在0-5范围内
        prediction = user_mean + numerator/denominator
        return max(0,min(5,prediction))
    else:
        return user_mean
if __name__ == '__main__':
    # 计算用户相似度矩阵
    sim_matrix = user_similarity(ratings)
    print(f"用户相似度矩阵：\n{np.round(sim_matrix,4)}")

    # 预测用户0对物品2的评分
    user_id = 0
    item_id = 2
    prediction = predict_rating(ratings,sim_matrix,user_id,item_id,k=2)
    print(f"用户{user_id}对物品{item_id}的预测评分:{prediction:.2f}")